package com.geek.gkpush.framework.job;

import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.geek.gkpush.framework.redis.RedisOperator;
import com.geek.gkpush.framework.redis.locker.RedisLocker;
import com.geek.gkpush.framework.trace.TraceContext;
import com.geek.gkpush.util.center.ConfigCenterUtils;
import com.geek.gkpush.util.enums.center.ConfigCenterEnum;
import com.geek.gkpush.util.enums.redis.RedisLockEnum;
import com.geek.gkpush.util.net.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * <h1>加锁的 SimpleJob 抽象类。</h1><br/>
 *
 * <h1><br/><a href="https://u.geekbang.org/subject/arch3/100614201">Java 业务架构实战营：https://u.geekbang.org/subject/arch3/100614201</a></h1><br/><h1><a href="https://gitee.com/ylimhhmily/BizArchPracticeCamp">Java 业务架构实战营代码地址：https://gitee.com/ylimhhmily/BizArchPracticeCamp</a></h1><br/><h1><a href="https://u.geekbang.org/subject/java4th/1001148?source=app_share">高级 Java 工程体系课 2023 版：https://u.geekbang.org/subject/java4th/1001148?source=app_share</a></h1><br/><h1><a href="https://time.geekbang.org/column/intro/100312101?tab=catalog">Dubbo 源码剖析与实战：https://time.geekbang.org/column/intro/100312101?tab=catalog</a></h1>
 *
 * @author hmilyylimh
 *         ^_^
 * @version 0.0.1
 *         ^_^
 * @date 2023/10/2
 *
 */
@Slf4j
public abstract class AbstractLockSimpleJob implements SimpleJob {

    @Autowired
    private RedisLocker redisLocker;

    /**
     * <h2>子类必须重写的 Job 执行逻辑方法。</h2>
     *
     * @param ctx
     */
    public abstract void doExecute(ShardingContext ctx);

    @Override
    public void execute(ShardingContext ctx) {
        // 准备 jobKey 的构成规则
        TraceContext.getOrCreate();
        String beanName = getClass().getSimpleName();
        String jobKey = String.join("_", beanName,
                String.valueOf(ctx.getShardingTotalCount()), String.valueOf(ctx.getShardingItem()));
        log.info("{}.execute begin, jobKey={}.", beanName, jobKey);

        // redis 加锁失败，则直接返回
        if (!tryLock(jobKey)) {
            log.info("{}.execute end, tryLock fail, redisValue={}", beanName, getValue(jobKey));
            return;
        }

        // redis 加锁成功，则安心执行业务逻辑
        try {
            // 抽象方法，必须需要子类去实现
            doExecute(ctx);

            // 执行成功，就打印个日志就好了
            log.info("{}.execute end, succ result.", beanName);
        } catch (Exception e) {
            log.error(String.format("%s.execute end, exception result.", beanName), e);
        } finally {
            // 最后，不管 Job 执行成功，或者失败，都需要释放 redis 中的 jobKey
            tryUnLock(jobKey);
        }
    }

    private boolean tryLock(String jobKey) {
        String redisKey = getRedisKey(jobKey);
        try {
            int expiryTime = ConfigCenterUtils.getOrDefault2Int(ConfigCenterEnum.DEFAULT_LOKJOB_EXPIRY_TIME_MILLIS);
            String netIp = IpUtils.getIp();
            return redisLocker.tryLock(redisKey, netIp, expiryTime);
        } catch (Exception e) {
            String errMsg = String.format("tryLock error, redisKey=%s.", redisKey);
            log.error(errMsg, e);
            return false;
        }
    }

    private boolean tryUnLock(String jobKey) {
        String redisKey = getRedisKey(jobKey);
        String netIp = IpUtils.getIp();
        try {
            return redisLocker.tryUnLock(redisKey, netIp);
        } catch (Exception e) {
            String errMsg = String.format("tryUnLock error, redisKey=%s.", redisKey);
            log.error(errMsg, e);
            return false;
        }
    }

    private Object getValue(String jobKey) {
        String redisKey = getRedisKey(jobKey);
        try {
            return RedisOperator.get(redisKey);
        } catch (Exception e) {
            String errMsg = String.format("redisDal.get error, redisKey=%s.", redisKey);
            log.error(errMsg, e);
            return "[RedisOperator.get error]";
        }
    }

    private String getRedisKey(String jobKey) {
        return String.join(":", RedisLockEnum.PREFIX_OF_LOCKJOB.getCode(), jobKey);
    }
}